/* 
 * This code is part of the smartlink project, see
 * 
 * 		http://code.google.com/p/smartlink/.
 * 
 * Copyright (C) 2008 Oliver Noelle
 * 
 * SmartLink is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * SmartLink is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with SmartLink.  If not, see <http://www.gnu.org/licenses/>.
 **/
package org.smartlink;

import java.util.List;

import org.smartlink.provider.SmartLink;

import android.app.Activity;
import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.view.Menu.Item;
import android.widget.Button;
import android.widget.EditText;

/**
 * The activity for editing a link. This can be used either to simply view a
 * link (Intent.VIEW_ACTION), view and edit a link (Intent.EDIT_ACTION), or
 * create a new link (Intent.INSERT_ACTION).
 */
public class LinkEditor extends Activity {

	private static final String TAG = "LinkEditor";

	/** request code for picking a target from a server app */
	private static final int PICK_TARGET_REQUEST = 1;

	private static final int TARGET_INDEX = 1;
	private static final int KEY_INDEX = 2;
	private static final int MODIFIED_INDEX = 3;
	private static final int DESCRIPTION_INDEX = 4;

	/**
	 * Standard projection for the interesting columns of a normal link.
	 */
	private static final String[] PROJECTION = new String[] { SmartLink.Links._ID, // 0
			SmartLink.Links.TARGET, // 1
			SmartLink.Links.KEY, // 2
			SmartLink.Links.MODIFIED_DATE, // 3
			SmartLink.Links.DESCRIPTION // 3
	};

	// This is our state data that is stored when freezing.
	private static final String ORIGINAL_CONTENT = "origContent";

	// Identifiers for our menu items.
	private static final int REVERT_ID = Menu.FIRST;
	private static final int DISCARD_ID = Menu.FIRST + 1;
	private static final int DELETE_ID = Menu.FIRST + 2;
	private static final int FIRST_SERVERAPP_MENU_ID = DELETE_ID + 1;

	/** The different distinct states the activity can be run in. */
	private int mState;
	/** editing a already existing link */
	private static final int STATE_EDIT = 0;
	/** editing a to-be-inserted new link */
	private static final int STATE_INSERT = 1;

	private Uri mURI;
	private Cursor mCursor;
	private EditText mKey;
	private EditText mTarget;
	private EditText mDesc;
	private String mOriginalContent;

	private boolean fTargetInserted = false;

	private List<ResolveInfo> activityList;

	@Override
	protected void onCreate(Bundle icicle) {
		super.onCreate(icicle);

		final Intent intent = getIntent();
		// final String type = intent.resolveType(this);

		final String action = intent.getAction();
		if (action.equals(Intent.EDIT_ACTION)) {
			// Requested to edit: set that state, and the data being edited.
			mState = STATE_EDIT;
			mURI = intent.getData();

		} else if (action.equals(Intent.INSERT_ACTION)) {
			// Requested to insert: set that state, and create a new entry
			// in the container.
			mState = STATE_INSERT;
			String key = intent.getStringExtra(SmartLink.Links.EXTRA_CREATE_LINK);
			ContentValues initialValues = new ContentValues();
			initialValues.put(SmartLink.Links.KEY, key);
			mURI = getContentResolver().insert(intent.getData(), initialValues);
			// If we were unable to create a new link, then just finish
			// this activity. A RESULT_CANCELED will be sent back to the
			// original activity if they requested a result.
			if (mURI == null) {
				Log.e(TAG, "Failed to insert new link into " + getIntent().getData());
				finish();
				return;
			}

			// The new entry was created, so assume all will end well and
			// set the result to be returned.
			setResult(RESULT_OK, mURI.toString());

		} else {
			// Whoops, unknown action! Bail.
			Log.e(TAG, "Unknown action, exiting");
			finish();
			return;
		}

		// Set the layout for this activity
		setContentView(R.layout.link_editor);
		mKey = (EditText) findViewById(R.id.thekey);
		mTarget = (EditText) findViewById(R.id.target);
		mDesc = (EditText) findViewById(R.id.description);

		Button okButton = (Button) findViewById(R.id.ok);
		okButton.setOnClickListener(new Button.OnClickListener() {
			@Override
			public void onClick(View arg0) {
				finish();
			}
		});
		Button cancelButton = (Button) findViewById(R.id.cancel);
		cancelButton.setOnClickListener(new Button.OnClickListener() {
			@Override
			public void onClick(View arg0) {
				cancelLink();
			}
		});

		// Get the link!
		mCursor = managedQuery(mURI, PROJECTION, null, null);
		// If an instance of this activity had previously stopped, we can
		// get the original text it started with.
		if (icicle != null) {
			mOriginalContent = icicle.getString(ORIGINAL_CONTENT);
		}
	}

	@Override
	protected void onResume() {
		super.onResume();

		// If we didn't have any trouble retrieving the data, it is now
		// time to get at the stuff.
		if (mCursor != null) {
			// Make sure we are at the one and only row in the cursor.
			mCursor.first();

			// Modify our overall title depending on the mode we are running in.
			if (mState == STATE_EDIT) {
				setTitle(getText(R.string.title_edit));
			} else if (mState == STATE_INSERT) {
				setTitle(getText(R.string.title_create));
			}

			if (!fTargetInserted) {
				// This is a little tricky: we may be resumed after previously
				// being paused/stopped. We want to put the new text in the text
				// view, but leave the user where they were (retain the cursor
				// position etc). This version of setText does that for us.
				String key = mCursor.getString(KEY_INDEX);
				mKey.setTextKeepState(key);
				String target = mCursor.getString(TARGET_INDEX);
				mTarget.setTextKeepState(target);
				String desc = mCursor.getString(DESCRIPTION_INDEX);
				mDesc.setTextKeepState(desc);

				// If we hadn't previously retrieved the original text, do so
				// now. This allows the user to revert their changes.
				if (mOriginalContent == null) {
					mOriginalContent = target;
				}
			} else {
				// do not update text from database because it was intentionally
				// modified by the sub-activity
				fTargetInserted = false;
			}
		} else {
			setTitle(getText(R.string.error_title));
			mTarget.setText(getText(R.string.error_message));
			mKey.setText(getText(R.string.error_message));
			mDesc.setText(getText(R.string.error_message));
		}
	}

	@Override
	protected void onFreeze(Bundle outState) {
		// Save away the original text, so we still have it if the activity
		// needs to be killed while paused.
		outState.putString(ORIGINAL_CONTENT, mOriginalContent);
	}

	@Override
	protected void onPause() {
		super.onPause();

		// The user is going somewhere else, so make sure their current
		// changes are safely saved away in the provider. We don't need
		// to do this if only editing.
		if (mCursor != null) {
			String target = mTarget.getText().toString();
			String key = mKey.getText().toString();
			String desc = mDesc.getText().toString();

			// Bump the modification time to now.
			mCursor.updateLong(MODIFIED_INDEX, System.currentTimeMillis());

			// Write our text back into the provider.
			mCursor.updateString(TARGET_INDEX, target);
			mCursor.updateString(KEY_INDEX, key);
			mCursor.updateString(DESCRIPTION_INDEX, desc);

			// Commit all of our changes to persistent storage. Note the
			// use of managedCommitUpdates() instead of
			// mCursor.commitUpdates() -- this lets Activity take care of
			// requerying the new data if needed.
			managedCommitUpdates(mCursor);
		}
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		super.onCreateOptionsMenu(menu);

		// Build the menus that are shown when editing.
		// Log.w(TAG, "getData(): " + getIntent().getData());
		if (mState == STATE_EDIT) {
			menu.add(0, REVERT_ID, R.string.menu_revert).setShortcut('0', 'r');
			menu.add(0, DELETE_ID, R.string.menu_delete_link).setShortcut('1', 'd');
		} else if (mState == STATE_INSERT) {
			menu.add(0, DISCARD_ID, R.string.menu_discard);
		} else {
			Log.w(TAG, "unexpected state");
		}

		// Log.w(TAG, "xxx uri: " + getIntent().getData());

		ComponentName component = new ComponentName(this, LinkEditor.class);
		Intent intent = new Intent("org.smartlink.action.PICK_TARGET_ACTION", mURI);
		intent.addCategory(Intent.ALTERNATIVE_CATEGORY);
		PackageManager pm = getPackageManager();
		activityList = pm.queryIntentActivityOptions(component, null, intent, 0);
		int index = FIRST_SERVERAPP_MENU_ID;
		for (ResolveInfo resolveInfo : activityList) {
			Item item = menu.add(0, index, resolveInfo.loadLabel(pm));
			item.setIcon(resolveInfo.loadIcon(pm));
			index++;
		}
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(Menu.Item item) {
		// Handle all of the possible menu actions.
		switch (item.getId()) {
		case DELETE_ID:
			deleteLink();
			finish();
			break;
		case DISCARD_ID:
			cancelLink();
			break;
		case REVERT_ID:
			cancelLink();
			break;
		default:
			// all registered server apps
			ResolveInfo resolveInfo = activityList.get(item.getId() - FIRST_SERVERAPP_MENU_ID);
			String packageName = resolveInfo.activityInfo.applicationInfo.packageName;
			String className = resolveInfo.activityInfo.name;
			ComponentName component = new ComponentName(packageName, className);
			Intent intent = new Intent("org.smartlink.action.PICK_TARGET_ACTION");
			intent.setComponent(component);
			startSubActivity(intent, PICK_TARGET_REQUEST);
		}
		return super.onOptionsItemSelected(item);
	}

	/** Listen for results of picking targets. */
	protected void onActivityResult(int requestCode, int resultCode, String data, Bundle extras) {
		if (resultCode == RESULT_CANCELED) {
			// picking a target was canceled
			return;
		}
		switch (requestCode) {
		case PICK_TARGET_REQUEST:
			mTarget.getText().clear();
			mTarget.getText().append(data);
			fTargetInserted = true;
			String desc = null;
			if (extras != null) {
				desc = extras.getString(Server.EXTRA_ITEM_DESCRIPTION);
			}
			if (desc == null) {
				// fallback if server does not provide a description: use uri as
				// description
				desc = data;
			}
			mDesc.getText().clear();
			mDesc.getText().append(desc);
		default:
			Log.w(TAG, "unexpected requestCode: " + requestCode);
			break;
		}
	}

	/**
	 * Take care of cancelling work on a link. Deletes the link if we had
	 * created it, otherwise reverts to the original text.
	 */
	private final void cancelLink() {
		if (mCursor != null) {
			if (mState == STATE_EDIT) {
				mCursor.updateString(TARGET_INDEX, mOriginalContent);
				mCursor.commitUpdates();
				mCursor.deactivate();
				mCursor = null;
			} else if (mState == STATE_INSERT) {
				deleteLink();
			}
		}
		setResult(RESULT_CANCELED);
		finish();
	}

	/**
	 * Take care of deleting a link. Simply deletes the entry.
	 */
	private final void deleteLink() {
		if (mCursor != null) {
			mTarget.setText("");
			mKey.setText("");
			mCursor.deleteRow();
			mCursor.deactivate();
			mCursor = null;
		}
	}
}
